package Tracer;

/*
 * @(#)RTColor.java     1.0         2009/09/12
 * Copyright(c)  2009 Autumn Large Scale Programming Group Feel   
 * Uppsala University,  Sweden
 * All right reserved.
 * 
 * file description
 */

/**
 * ClassName : RTColor This class is used to describe a point in the picture
 * using three values(r,g,b) and these values signify the amount of red,green
 * and blue in the point.
 * 
 * @version 1.0 Sep. 12th 2009
 * @author forrest lorraine eagle
 */

public class RTColor {
	// RGB value is stored by byte
	/**
	 * the red component of color
	 */
	private byte cRed = 0;
	/**
	 * the green component of color
	 */
	private byte cGreen = 0;
	/**
	 * the blue component of color
	 */
	private byte cBlue = 0;

	// TRANSFORMER is a constant as a factor for transforming integer to byte
	// and in verse.
	/**
	 * a transformer to change between byte and integer
	 */
	private final int TRANSFORMER = 0xFF;

	/**
	 * Class Constructor for float RGB
	 * 
	 * @param red
	 *            the red value of a color using the number in the range of
	 *            0.0~1.0
	 * @param green
	 *            the green value of a color using the number in the range of
	 *            0.0~1.0
	 * @param blue
	 *            the blue value of a color using the number in the range of
	 *            0.0~1.0
	 */
	public RTColor(final float red, final float green, final float blue) {
		if (red < 0.0 || red > 1.0 || green < 0.0 || green > 1.0 || blue < 0.0
				|| blue > 1.0) {
			throw new IllegalArgumentException("Argument out of range!");
		} else {
			this.cRed = iTob(Math.round(red * 255f));
			this.cGreen = iTob(Math.round(green * 255f));
			this.cBlue = iTob(Math.round(blue * 255f));
		}
	}

	/**
	 * Class Constructor for integer RGB
	 * 
	 * @param red
	 *            the red value of a color using the number in the range of
	 *            0~255
	 * @param green
	 *            the green value of a color using the number in the range of
	 *            0~255
	 * @param blue
	 *            the blue value of a color using the number in the range of
	 *            0~255
	 */
	public RTColor(final int red, final int green, final int blue) {
		if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0
				|| blue > 255) {
			throw new IllegalArgumentException("Argument out of range!");
		} else {
			this.cRed = iTob(red);
			this.cGreen = iTob(green);
			this.cBlue = iTob(blue);
		}
	}

	/**
	 * Class copy constructor
	 * 
	 * @param color
	 *            a being copied color
	 */
	public RTColor(final RTColor color) {
		float redValueF = color.getRedf();
		float greenValueF = color.getGreenf();
		float blueValueF = color.getBluef();

		if (redValueF < 0.0 || redValueF > 1.0 || greenValueF < 0.0
				|| greenValueF > 1.0 || blueValueF < 0.0 || blueValueF > 1.0) {
			throw new IllegalArgumentException("Argument out of range!");
		} else {
			this.cRed = iTob(Math.round(color.getRed()));
			this.cGreen = iTob(Math.round(color.getGreen()));
			this.cBlue = iTob(Math.round(color.getBlue()));
		}
	}

	/**
	 * A RTColor constant value for Black Color
	 */
	public final static RTColor BLACK = new RTColor(0.0f, 0.0f, 0.0f);

	/**
	 * A RTColor constant value for White Color
	 */
	public final static RTColor WHITE = new RTColor(1.0f, 1.0f, 1.0f);

	/**
	 * set the color with an already existing color
	 * 
	 * @param color
	 *            a being copied color
	 */
	public final void setColor(final RTColor color) {
		float redValueF = color.getRedf();
		float greenValueF = color.getGreenf();
		float blueValueF = color.getBluef();

		if (redValueF < 0.0 || redValueF > 1.0 || greenValueF < 0.0
				|| greenValueF > 1.0 || blueValueF < 0.0 || blueValueF > 1.0) {
			throw new IllegalArgumentException("Argument out of range!");
		} else {
			this.cRed = iTob(Math.round(color.getRed()));
			this.cGreen = iTob(Math.round(color.getGreen()));
			this.cBlue = iTob(Math.round(color.getBlue()));
		}
	}

	/**
	 * This private function is used to transform the value of RGB from integer
	 * to byte.
	 * 
	 * @param ivalue
	 *            integer
	 * @return the byte
	 */
	private byte iTob(final int ivalue) {
		byte bvalue = (byte) (ivalue & TRANSFORMER);
		return bvalue;
	}

	/**
	 * This private function is used to transform the value of RGB from byte to
	 * integer.
	 * 
	 * @param bvalue
	 *            byte
	 * @return the integer
	 */
	private int bToi(final byte bvalue) {
		int ivalue = bvalue & TRANSFORMER;
		return ivalue;
	}

	/**
	 * Get the red value of a color in the form of integer
	 * 
	 * @return the red value of the color instance using the form of number in
	 *         the range of 0~255
	 */
	public final int getRed() {
		return bToi(cRed);
	}

	/**
	 * Get the green value of a color in the form of integer
	 * 
	 * @return the green value of the color instance using the form of number in
	 *         the range of 0~255
	 */
	public final int getGreen() {
		return bToi(cGreen);
	}

	/**
	 * Get the blue value of a color in the form of integer
	 * 
	 * @return the blue value of the color instance using the form of number in
	 *         the range of 0~255
	 */
	public final int getBlue() {
		return bToi(cBlue);
	}

	/**
	 * Get the red value of a color in the form of float
	 * 
	 * @return the red value of the color instance using the form of number in
	 *         the range of 0.0~1.0
	 */
	public final float getRedf() {
		return (float) bToi(cRed) / 255;
	}

	/**
	 * Get the green value of a color in the form of float
	 * 
	 * @return the green value of the color instance using the form of number in
	 *         the range of 0.0~1.0
	 */
	public final float getGreenf() {
		return (float) bToi(cGreen) / 255;
	}

	/**
	 * Get the blue value of a color in the form of float
	 * 
	 * @return the blue value of the color instance using the form of number in
	 *         the range of 0.0~1.0
	 */
	public final float getBluef() {
		return (float) bToi(cBlue) / 255;
	}

	/**
	 * set Red color of integer type
	 * 
	 * @param r
	 */

	private final void setRed(int r) {
		if (r < 0 || r > 255) {
			throw new IllegalArgumentException("Argument red out of range!");
		}
		this.cRed = iTob(r);
	}

	/**
	 * set Green color of integer type
	 * 
	 * @param g
	 */
	private final void setGreen(int g) {
		if (g < 0 || g > 255) {
			throw new IllegalArgumentException("Argument green out of range!"
					+ g);
		}
		this.cGreen = iTob(g);
	}

	/**
	 * set Blue color of integer type
	 * 
	 * @param b
	 *            the blue color
	 */
	private final void setBlue(final int b) {
		if (b < 0 || b > 255) {
			throw new IllegalArgumentException("Argument blue out of range!");
		}
		this.cBlue = iTob(b);
	}

	/**
	 * Compare the input color with self, if both colors are exactly equal,
	 * return TRUE, otherwise return FALSE.
	 * 
	 * @param color
	 *            The input color to be compared.
	 * @return TRUE if the input color is equal to self, otherwise FALSE.
	 */
	public final boolean isEqual(final RTColor color) {
		return (color.getBlue() == this.getBlue()
				&& color.getRed() == this.getRed() && color.getGreen() == this
				.getGreen()) ? true : false;
	}

	/**
	 * Multiply a number to the R G B value of RTColor separately
	 * 
	 * @param origin
	 *            The original color before multiplication
	 * @param factor
	 *            The number to multiply
	 * @return RTColor the result color after multiplication
	 */
	public static RTColor multByNumber(RTColor origin, double factor) {
		RTColor result = new RTColor(0, 0, 0);
		if (Math.round(origin.getRed() * factor) > 255) {
			result.setRed(255);
		} else if (Math.round(origin.getRed() * factor) < 0) {
			result.setRed(0);
		} else {
			result.setRed((int) Math.round(origin.getRed() * factor));
		}
		if (Math.round(origin.getGreen() * factor) > 255) {
			result.setGreen(255);
		} else if (Math.round(origin.getGreen() * factor) < 0) {
			result.setGreen(0);
		} else {
			result.setGreen((int) Math.round(origin.getGreen() * factor));
		}
		if (Math.round(origin.getBlue() * factor) > 255) {
			result.setBlue(255);
		} else if (Math.round(origin.getBlue() * factor) < 0) {
			result.setBlue(0);
		} else {
			result.setBlue((int) Math.round(origin.getBlue() * factor));
		}
		return result;
	}

	/**
	 * Combine two RTColor together into one new RTColor instance
	 * 
	 * @param color1
	 *            one color to be added
	 * @param color2
	 *            another color to be added
	 * @return RTColor the result color after combination
	 */
	public static RTColor combineColor(RTColor color1, RTColor color2) {
		return new RTColor(Math.min(color1.getRed() + color2.getRed(), 255),
				Math.min(color1.getGreen() + color2.getGreen(), 255), Math.min(
						color1.getBlue() + color2.getBlue(), 255));
	}

}